/*
 * Generic converter template for a selected ASN.1 type.
 * SPDX-FileCopyrightText: (c) 2003, 2004 Lev Walkin <vlm@lionet.info>. All rights reserved.
 * SPDX-License-Identifier: BSD-1-Clause
 *
 * To compile with your own ASN.1 type, please redefine the PDU as shown:
 *
 * cc -DPDU=MyCustomType -o myDecoder.o -c converter-sample.c
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <asn_application.h>
#include <asn_internal.h> /* for _ASN_DEFAULT_STACK_MAX */
#include <errno.h>        /* for errno */
#include <stdio.h>
#include <stdlib.h> /* for atoi(3) */
#include <string.h> /* for strerror(3) */
#include <sys/types.h>
#include <sysexits.h> /* for EX_* exit codes */
#include <unistd.h>   /* for getopt(3) */

/* Convert "Type" defined by -DPDU into "asn_DEF_Type" */
#define ASN_DEF_PDU(t) asn_DEF_##t
#define DEF_PDU_Type(t) ASN_DEF_PDU(t)
#define PDU_Type DEF_PDU_Type(PDU)

extern asn_TYPE_descriptor_t PDU_Type; /* ASN.1 type to be decoded */
#ifdef ASN_PDU_COLLECTION              /* Generated by asn1c: -pdu=... */
extern asn_TYPE_descriptor_t *asn_pdu_collection[];
#endif

/*
 * Open file and parse its contents.
 */
static void *data_decode_from_file(asn_TYPE_descriptor_t *pduType, FILE *file,
    const char *name, ssize_t suggested_bufsize,
    int first_pdu);
static int write_out(const void *buffer, size_t size, void *key);
static FILE *argument_to_file(char *av[], int idx);
static char *argument_to_name(char *av[], int idx);

int opt_debug;         /* -d (or -dd) */
static int opt_check;  /* -c (constraints checking) */
static int opt_stack;  /* -s (maximum stack size) */
static int opt_nopad;  /* -per-nopad (PER input is not padded) */
static int opt_onepdu; /* -1 (decode single PDU) */

/* Input data format selector */
static enum input_format {
    INP_BER, /* -iber: BER input */
    INP_XER, /* -ixer: XER input */
    INP_PER  /* -iper: Unaligned PER input */
} iform;     /* -i<format> */

/* Output data format selector */
static enum output_format {
    OUT_XER,  /* -oxer: XER (XML) output */
    OUT_DER,  /* -oder: DER (BER) output */
    OUT_PER,  /* -oper: Unaligned PER output */
    OUT_TEXT, /* -otext: semi-structured text */
    OUT_NULL  /* -onull: No pretty-printing */
} oform;      /* -o<format> */

#ifdef JUNKTEST /* Enable -J <probability> */
#define JUNKOPT "J:"
static double opt_jprob; /* Junk bit probability */
static int junk_failures;
static void junk_bytes_with_probability(uint8_t *, size_t, double prob);
#else
#define JUNKOPT
#endif

/* Debug output function */
static inline void DEBUG(const char *fmt, ...)
{
    va_list ap;
    if (!opt_debug)
        {
            return;
        }
    fprintf(stderr, "AD: ");
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, "\n");
}

int main(int ac, char *av[])
{
    static asn_TYPE_descriptor_t *pduType = &PDU_Type;
    ssize_t suggested_bufsize = 8192; /* close or equal to stdio buffer */
    int number_of_iterations = 1;
    int num;
    int ch;

    /* Figure out if Unaligned PER needs to be default */
    if (pduType->uper_decoder)
        {
            iform = INP_PER;
        }

    /*
     * Process the command-line arguments.
     */
    while ((ch = getopt(ac, av, "i:o:1b:cdn:p:hs:" JUNKOPT)) != -1)
        {
            switch (ch)
                {
                case 'i':
                    if (optarg[0] == 'b')
                        {
                            iform = INP_BER;
                            break;
                        }
                    if (optarg[0] == 'x')
                        {
                            iform = INP_XER;
                            break;
                        }
                    if (pduType->uper_decoder && optarg[0] == 'p')
                        {
                            iform = INP_PER;
                            break;
                        }
                    fprintf(stderr,
                        "-i<format>: '%s': improper format selector\n",
                        optarg);
                    exit(EX_UNAVAILABLE);
                case 'o':
                    if (optarg[0] == 'd')
                        {
                            oform = OUT_DER;
                            break;
                        }
                    if (pduType->uper_encoder && optarg[0] == 'p')
                        {
                            oform = OUT_PER;
                            break;
                        }
                    if (optarg[0] == 'x')
                        {
                            oform = OUT_XER;
                            break;
                        }
                    if (optarg[0] == 't')
                        {
                            oform = OUT_TEXT;
                            break;
                        }
                    if (optarg[0] == 'n')
                        {
                            oform = OUT_NULL;
                            break;
                        }
                    fprintf(stderr,
                        "-o<format>: '%s': improper format selector\n",
                        optarg);
                    exit(EX_UNAVAILABLE);
                case '1':
                    opt_onepdu = 1;
                    break;
                case 'b':
                    suggested_bufsize = atoi(optarg);
                    if (suggested_bufsize < 1 ||
                        suggested_bufsize > 16 * 1024 * 1024)
                        {
                            fprintf(stderr,
                                "-b %s: Improper buffer size (1..16M)\n",
                                optarg);
                            exit(EX_UNAVAILABLE);
                        }
                    break;
                case 'c':
                    opt_check = 1;
                    break;
                case 'd':
                    opt_debug++; /* Double -dd means ASN.1 debug */
                    break;
                case 'n':
                    number_of_iterations = atoi(optarg);
                    if (number_of_iterations < 1)
                        {
                            fprintf(stderr,
                                "-n %s: Improper iterations count\n",
                                optarg);
                            exit(EX_UNAVAILABLE);
                        }
                    break;
                case 'p':
                    if (strcmp(optarg, "er-nopad") == 0)
                        {
                            opt_nopad = 1;
                            break;
                        }
#ifdef ASN_PDU_COLLECTION
                    if (strcmp(optarg, "list") == 0)
                        {
                            asn_TYPE_descriptor_t **pdu = asn_pdu_collection;
                            fprintf(stderr, "Available PDU types:\n");
                            for (; *pdu; pdu++) printf("%s\n", (*pdu)->name);
                            exit(0);
                        }
                    else if (optarg[0] >= 'A' && optarg[0] <= 'Z')
                        {
                            asn_TYPE_descriptor_t **pdu = asn_pdu_collection;
                            while (*pdu && strcmp((*pdu)->name, optarg)) pdu++;
                            if (*pdu)
                                {
                                    pduType = *pdu;
                                    break;
                                }
                            fprintf(stderr, "-p %s: Unrecognized PDU\n",
                                optarg);
                        }
#endif /* ASN_PDU_COLLECTION */
                    fprintf(stderr, "-p %s: Unrecognized option\n", optarg);
                    exit(EX_UNAVAILABLE);
                case 's':
                    opt_stack = atoi(optarg);
                    if (opt_stack < 0)
                        {
                            fprintf(stderr,
                                "-s %s: Non-negative value expected\n",
                                optarg);
                            exit(EX_UNAVAILABLE);
                        }
                    break;
#ifdef JUNKTEST
                case 'J':
                    opt_jprob = strtod(optarg, 0);
                    if (opt_jprob <= 0.0 || opt_jprob > 1.0)
                        {
                            fprintf(stderr,
                                "-J %s: Probability range 0..1 expected \n",
                                optarg);
                            exit(EX_UNAVAILABLE);
                        }
                    break;
#endif /* JUNKTEST */
                case 'h':
                default:
#ifdef ASN_CONVERTER_TITLE
#define _AXS(x) #x
#define _ASX(x) _AXS(x)
                    fprintf(stderr, "%s\n", _ASX(ASN_CONVERTER_TITLE));
#endif
                    fprintf(stderr, "Usage: %s [options] <data.ber> ...\n",
                        av[0]);
                    fprintf(stderr, "Where options are:\n");
                    if (pduType->uper_decoder)
                        {
                            fprintf(
                                stderr,
                                "  -iper        Input is in Unaligned PER (Packed "
                                "Encoding Rules) (DEFAULT)\n");
                        }
                    fprintf(stderr,
                        "  -iber        Input is in BER (Basic Encoding "
                        "Rules)%s\n",
                        iform == INP_PER ? "" : " (DEFAULT)");
                    fprintf(stderr,
                        "  -ixer        Input is in XER (XML Encoding "
                        "Rules)\n");
                    if (pduType->uper_encoder)
                        {
                            fprintf(
                                stderr,
                                "  -oper        Output in Unaligned PER (Packed "
                                "Encoding "
                                "Rules)\n");
                        }
                    fprintf(
                        stderr,
                        "  -oder        Output in DER (Distinguished Encoding "
                        "Rules)\n"
                        "  -oxer        Output in XER (XML Encoding Rules) "
                        "(DEFAULT)\n"
                        "  -otext       Output in plain semi-structured text "
                        "(dump)\n"
                        "  -onull       Verify (decode) input, but do not "
                        "output\n");
                    if (pduType->uper_decoder)
                        {
                            fprintf(stderr,
                                "  -per-nopad   Assume PER PDUs are not padded "
                                "(-iper)\n");
                        }
#ifdef ASN_PDU_COLLECTION
                    fprintf(stderr,
                        "  -p <PDU>     Specify PDU type to decode\n"
                        "  -p list      List available PDUs\n");
#endif /* ASN_PDU_COLLECTION */
                    fprintf(
                        stderr,
                        "  -1           Decode only the first PDU in file\n"
                        "  -b <size>    Set the i/o buffer size (default is "
                        "%ld)\n"
                        "  -c           Check ASN.1 constraints after "
                        "decoding\n"
                        "  -d           Enable debugging (-dd is even better)\n"
                        "  -n <num>     Process files <num> times\n"
                        "  -s <size>    Set the stack usage limit (default is "
                        "%d)\n"
#ifdef JUNKTEST
                        "  -J <prob>    Set random junk test bit garbaging "
                        "probability\n"
#endif
                        ,
                        (long)suggested_bufsize, _ASN_DEFAULT_STACK_MAX);
                    exit(EX_USAGE);
                }
        }

    ac -= optind;
    av += optind;

    if (ac < 1)
        {
            fprintf(stderr,
                "%s: No input files specified. "
                "Try '-h' for more information\n",
                av[-optind]);
            exit(EX_USAGE);
        }

    setvbuf(stdout, 0, _IOLBF, 0);

    for (num = 0; num < number_of_iterations; num++)
        {
            int ac_i;
            /*
             * Process all files in turn.
             */
            for (ac_i = 0; ac_i < ac; ac_i++)
                {
                    asn_enc_rval_t erv;
                    void *structure; /* Decoded structure */
                    FILE *file = argument_to_file(av, ac_i);
                    char *name = argument_to_name(av, ac_i);
                    int first_pdu;

                    for (first_pdu = 1; first_pdu || !opt_onepdu; first_pdu = 0)
                        {
                            /*
                             * Decode the encoded structure from file.
                             */
                            structure = data_decode_from_file(
                                pduType, file, name, suggested_bufsize,
                                first_pdu);
                            if (!structure)
                                {
                                    if (errno)
                                        {
                                            /* Error message is already printed
                                             */
                                            exit(EX_DATAERR);
                                        }
                                    else
                                        {
                                            /* EOF */
                                            break;
                                        }
                                }

                            /* Check ASN.1 constraints */
                            if (opt_check)
                                {
                                    char errbuf[128];
                                    size_t errlen = sizeof(errbuf);
                                    if (asn_check_constraints(pduType,
                                            structure, errbuf,
                                            &errlen))
                                        {
                                            fprintf(stderr,
                                                "%s: ASN.1 constraint "
                                                "check failed: %s\n",
                                                name, errbuf);
                                            exit(EX_DATAERR);
                                        }
                                }

                            switch (oform)
                                {
                                case OUT_NULL:
#ifdef JUNKTEST
                                    if (opt_jprob == 0.0)
#endif
                                        fprintf(
                                            stderr,
                                            "%s: decoded successfully\n",
                                            name);
                                    break;
                                case OUT_TEXT: /* -otext */
                                    asn_fprint(stdout, pduType, structure);
                                    break;
                                case OUT_XER: /* -oxer */
                                    if (xer_fprint(stdout, pduType,
                                            structure))
                                        {
                                            fprintf(stderr,
                                                "%s: Cannot convert %s "
                                                "into XML\n",
                                                name, pduType->name);
                                            exit(EX_UNAVAILABLE);
                                        }
                                    break;
                                case OUT_DER:
                                    erv = der_encode(pduType, structure,
                                        write_out, stdout);
                                    if (erv.encoded < 0)
                                        {
                                            fprintf(stderr,
                                                "%s: Cannot convert %s "
                                                "into DER\n",
                                                name, pduType->name);
                                            exit(EX_UNAVAILABLE);
                                        }
                                    DEBUG("Encoded in %ld bytes of DER",
                                        (long)erv.encoded);
                                    break;
                                case OUT_PER:
                                    erv = uper_encode(pduType, structure,
                                        write_out, stdout);
                                    if (erv.encoded < 0)
                                        {
                                            fprintf(stderr,
                                                "%s: Cannot convert %s "
                                                "into Unaligned PER\n",
                                                name, pduType->name);
                                            exit(EX_UNAVAILABLE);
                                        }
                                    DEBUG("Encoded in %ld bits of UPER",
                                        (long)erv.encoded);
                                    break;
                                }

                            ASN_STRUCT_FREE(*pduType, structure);
                        }

                    if (file && file != stdin)
                        {
                            fclose(file);
                        }
                }
        }

#ifdef JUNKTEST
    if (opt_jprob > 0.0)
        {
            fprintf(stderr, "Junked %f OK (%d/%d)\n", opt_jprob, junk_failures,
                number_of_iterations);
        }
#endif /* JUNKTEST */

    return 0;
}

static struct dynamic_buffer
{
    uint8_t *data;       /* Pointer to the data bytes */
    size_t offset;       /* Offset from the start */
    size_t length;       /* Length of meaningful contents */
    size_t unbits;       /* Unused bits in the last byte */
    size_t allocated;    /* Allocated memory for data */
    int nreallocs;       /* Number of data reallocations */
    off_t bytes_shifted; /* Number of bytes ever shifted */
} DynamicBuffer;

static void buffer_dump()
{
    uint8_t *p = DynamicBuffer.data + DynamicBuffer.offset;
    uint8_t *e = p + DynamicBuffer.length - (DynamicBuffer.unbits ? 1 : 0);
    if (!opt_debug)
        {
            return;
        }
    DEBUG("Buffer: { d=%p, o=%ld, l=%ld, u=%ld, a=%ld, s=%ld }",
        DynamicBuffer.data, (long)DynamicBuffer.offset,
        (long)DynamicBuffer.length, (long)DynamicBuffer.unbits,
        (long)DynamicBuffer.allocated, (long)DynamicBuffer.bytes_shifted);
    for (; p < e; p++)
        {
            fprintf(stderr, " %c%c%c%c%c%c%c%c", ((*p >> 7) & 1) ? '1' : '0',
                ((*p >> 6) & 1) ? '1' : '0', ((*p >> 5) & 1) ? '1' : '0',
                ((*p >> 4) & 1) ? '1' : '0', ((*p >> 3) & 1) ? '1' : '0',
                ((*p >> 2) & 1) ? '1' : '0', ((*p >> 1) & 1) ? '1' : '0',
                ((*p >> 0) & 1) ? '1' : '0');
        }
    if (DynamicBuffer.unbits)
        {
            unsigned int shift;
            fprintf(stderr, " ");
            for (shift = 7; shift >= DynamicBuffer.unbits; shift--)
                {
                    fprintf(stderr, "%c", ((*p >> shift) & 1) ? '1' : '0');
                }
            fprintf(stderr, " %ld:%ld\n", (long)DynamicBuffer.length - 1,
                (long)8 - DynamicBuffer.unbits);
        }
    else
        {
            fprintf(stderr, " %ld\n", DynamicBuffer.length);
        }
}

/*
 * Move the buffer content left N bits, possibly joining it with
 * preceding content.
 */
static void buffer_shift_left(size_t offset, int bits)
{
    uint8_t *ptr = DynamicBuffer.data + DynamicBuffer.offset + offset;
    uint8_t *end =
        DynamicBuffer.data + DynamicBuffer.offset + DynamicBuffer.length - 1;

    if (!bits)
        {
            return;
        }

    DEBUG("Shifting left %d bits off %ld (o=%ld, u=%ld, l=%ld)", bits,
        (long)offset, (long)DynamicBuffer.offset, (long)DynamicBuffer.unbits,
        (long)DynamicBuffer.length);

    if (offset)
        {
            int right;
            right = ptr[0] >> (8 - bits);

            DEBUG("oleft: %c%c%c%c%c%c%c%c", ((ptr[-1] >> 7) & 1) ? '1' : '0',
                ((ptr[-1] >> 6) & 1) ? '1' : '0',
                ((ptr[-1] >> 5) & 1) ? '1' : '0',
                ((ptr[-1] >> 4) & 1) ? '1' : '0',
                ((ptr[-1] >> 3) & 1) ? '1' : '0',
                ((ptr[-1] >> 2) & 1) ? '1' : '0',
                ((ptr[-1] >> 1) & 1) ? '1' : '0',
                ((ptr[-1] >> 0) & 1) ? '1' : '0');

            DEBUG("oriht: %c%c%c%c%c%c%c%c", ((ptr[0] >> 7) & 1) ? '1' : '0',
                ((ptr[0] >> 6) & 1) ? '1' : '0',
                ((ptr[0] >> 5) & 1) ? '1' : '0',
                ((ptr[0] >> 4) & 1) ? '1' : '0',
                ((ptr[0] >> 3) & 1) ? '1' : '0',
                ((ptr[0] >> 2) & 1) ? '1' : '0',
                ((ptr[0] >> 1) & 1) ? '1' : '0',
                ((ptr[0] >> 0) & 1) ? '1' : '0');

            DEBUG(
                "mriht: %c%c%c%c%c%c%c%c", ((right >> 7) & 1) ? '1' : '0',
                ((right >> 6) & 1) ? '1' : '0', ((right >> 5) & 1) ? '1' : '0',
                ((right >> 4) & 1) ? '1' : '0', ((right >> 3) & 1) ? '1' : '0',
                ((right >> 2) & 1) ? '1' : '0', ((right >> 1) & 1) ? '1' : '0',
                ((right >> 0) & 1) ? '1' : '0');

            ptr[-1] = (ptr[-1] & (0xff << bits)) | right;

            DEBUG("after: %c%c%c%c%c%c%c%c", ((ptr[-1] >> 7) & 1) ? '1' : '0',
                ((ptr[-1] >> 6) & 1) ? '1' : '0',
                ((ptr[-1] >> 5) & 1) ? '1' : '0',
                ((ptr[-1] >> 4) & 1) ? '1' : '0',
                ((ptr[-1] >> 3) & 1) ? '1' : '0',
                ((ptr[-1] >> 2) & 1) ? '1' : '0',
                ((ptr[-1] >> 1) & 1) ? '1' : '0',
                ((ptr[-1] >> 0) & 1) ? '1' : '0');
        }

    buffer_dump();

    for (; ptr < end; ptr++)
        {
            int right = ptr[1] >> (8 - bits);
            *ptr = (*ptr << bits) | right;
        }
    *ptr <<= bits;

    DEBUG("Unbits [%d=>", (int)DynamicBuffer.unbits);
    if (DynamicBuffer.unbits == 0)
        {
            DynamicBuffer.unbits += bits;
        }
    else
        {
            DynamicBuffer.unbits += bits;
            if (DynamicBuffer.unbits > 7)
                {
                    DynamicBuffer.unbits -= 8;
                    DynamicBuffer.length--;
                    DynamicBuffer.bytes_shifted++;
                }
        }
    DEBUG("Unbits =>%d]", (int)DynamicBuffer.unbits);

    buffer_dump();

    DEBUG("Shifted. Now (o=%ld, u=%ld l=%ld)", (long)DynamicBuffer.offset,
        (long)DynamicBuffer.unbits, (long)DynamicBuffer.length);
}

/*
 * Ensure that the buffer contains at least this amount of free space.
 */
static void add_bytes_to_buffer(const void *data2add, size_t bytes)
{
    if (bytes == 0)
        {
            return;
        }

    DEBUG("=> add_bytes(%ld) { o=%ld l=%ld u=%ld, s=%ld }", (long)bytes,
        (long)DynamicBuffer.offset, (long)DynamicBuffer.length,
        (long)DynamicBuffer.unbits, (long)DynamicBuffer.allocated);

    if (DynamicBuffer.allocated >=
        (DynamicBuffer.offset + DynamicBuffer.length + bytes))
        {
            DEBUG("\tNo buffer reallocation is necessary");
        }
    else if (bytes <= DynamicBuffer.offset)
        {
            DEBUG("\tContents shifted by %ld", DynamicBuffer.offset);

            /* Shift the buffer contents */
            memmove(DynamicBuffer.data,
                DynamicBuffer.data + DynamicBuffer.offset,
                DynamicBuffer.length);
            DynamicBuffer.bytes_shifted += DynamicBuffer.offset;
            DynamicBuffer.offset = 0;
        }
    else
        {
            size_t newsize = (DynamicBuffer.allocated << 2) + bytes;
            void *p = MALLOC(newsize);
            if (!p)
                {
                    perror("malloc()");
                    exit(EX_OSERR);
                }
            memcpy(p, DynamicBuffer.data + DynamicBuffer.offset,
                DynamicBuffer.length);
            FREEMEM(DynamicBuffer.data);
            DynamicBuffer.data = (uint8_t *)p;
            DynamicBuffer.offset = 0;
            DynamicBuffer.allocated = newsize;
            DynamicBuffer.nreallocs++;
            DEBUG("\tBuffer reallocated to %ld (%d time)", newsize,
                DynamicBuffer.nreallocs);
        }

    memcpy(DynamicBuffer.data + DynamicBuffer.offset + DynamicBuffer.length,
        data2add, bytes);
    DynamicBuffer.length += bytes;
    if (DynamicBuffer.unbits)
        {
            int bits = DynamicBuffer.unbits;
            DynamicBuffer.unbits = 0;
            buffer_shift_left(DynamicBuffer.length - bytes, bits);
        }

    DEBUG("<= add_bytes(%ld) { o=%ld l=%ld u=%ld, s=%ld }", (long)bytes,
        (long)DynamicBuffer.offset, (long)DynamicBuffer.length,
        (long)DynamicBuffer.unbits, (long)DynamicBuffer.allocated);
}

static void *data_decode_from_file(asn_TYPE_descriptor_t *pduType, FILE *file,
    const char *name, ssize_t suggested_bufsize,
    int on_first_pdu)
{
    static uint8_t *fbuf;
    static ssize_t fbuf_size;
    static asn_codec_ctx_t s_codec_ctx;
    asn_codec_ctx_t *opt_codec_ctx = 0;
    void *structure = 0;
    asn_dec_rval_t rval;
    size_t old_offset;
    size_t new_offset;
    int tolerate_eof;
    size_t rd;

    if (!file)
        {
            fprintf(stderr, "%s: %s\n", name, strerror(errno));
            errno = EINVAL;
            return 0;
        }

    if (opt_stack)
        {
            s_codec_ctx.max_stack_size = opt_stack;
            opt_codec_ctx = &s_codec_ctx;
        }

    DEBUG("Processing %s", name);

    /* prepare the file buffer */
    if (fbuf_size != suggested_bufsize)
        {
            fbuf = (uint8_t *)REALLOC(fbuf, suggested_bufsize);
            if (!fbuf)
                {
                    perror("realloc()");
                    exit(EX_OSERR);
                }
            fbuf_size = suggested_bufsize;
        }

    if (on_first_pdu)
        {
            DynamicBuffer.offset = 0;
            DynamicBuffer.length = 0;
            DynamicBuffer.unbits = 0;
            DynamicBuffer.allocated = 0;
            DynamicBuffer.bytes_shifted = 0;
            DynamicBuffer.nreallocs = 0;
        }

    old_offset = DynamicBuffer.bytes_shifted + DynamicBuffer.offset;

    /* Pretend immediate EOF */
    rval.code = RC_WMORE;
    rval.consumed = 0;

    for (tolerate_eof = 1; /* Allow EOF first time buffer is non-empty */
         (rd = fread(fbuf, 1, fbuf_size, file)) || feof(file) == 0 ||
         (tolerate_eof && DynamicBuffer.length);)
        {
            int ecbits = 0; /* Extra consumed bits in case of PER */
            uint8_t *i_bptr;
            size_t i_size;

            /*
             * Copy the data over, or use the original buffer.
             */
            if (DynamicBuffer.allocated)
                {
                    /* Append new data into the existing dynamic buffer */
                    add_bytes_to_buffer(fbuf, rd);
                    i_bptr = DynamicBuffer.data + DynamicBuffer.offset;
                    i_size = DynamicBuffer.length;
                }
            else
                {
                    i_bptr = fbuf;
                    i_size = rd;
                }

            DEBUG("Decoding %ld bytes", (long)i_size);

#ifdef JUNKTEST
            junk_bytes_with_probability(i_bptr, i_size, opt_jprob);
#endif

            switch (iform)
                {
                case INP_BER:
                    rval = ber_decode(opt_codec_ctx, pduType, &structure,
                        i_bptr, i_size);
                    break;
                case INP_XER:
                    rval = xer_decode(opt_codec_ctx, pduType, &structure,
                        i_bptr, i_size);
                    break;
                case INP_PER:
                    if (opt_nopad)
                        {
                            rval = uper_decode(opt_codec_ctx, pduType,
                                &structure, i_bptr, i_size, 0,
                                DynamicBuffer.unbits);
                        }
                    else
                        {
                            rval = uper_decode_complete(opt_codec_ctx, pduType,
                                &structure, i_bptr,
                                i_size);
                        }
                    switch (rval.code)
                        {
                        case RC_OK:
                            /* Fall through */
                        case RC_FAIL:
                            if (opt_nopad)
                                {
                                    /* uper_decode() returns bits! */
                                    /* Extra bits */
                                    ecbits = rval.consumed % 8;
                                    /* Convert into bytes! */
                                    rval.consumed /= 8;
                                }
                            break;
                        case RC_WMORE:
                            /* PER does not support restartability */
                            ASN_STRUCT_FREE(*pduType, structure);
                            structure = 0;
                            rval.consumed = 0;
                            /* Continue accumulating data */
                            break;
                        }
                    break;
                }
            DEBUG("decode(%ld) consumed %ld+%db (%ld), code %d",
                (long)DynamicBuffer.length, (long)rval.consumed, ecbits,
                (long)i_size, rval.code);

            if (DynamicBuffer.allocated == 0)
                {
                    /*
                     * Flush remainder into the intermediate buffer.
                     */
                    if (rval.code != RC_FAIL && rval.consumed < rd)
                        {
                            add_bytes_to_buffer(fbuf + rval.consumed,
                                rd - rval.consumed);
                            buffer_shift_left(0, ecbits);
                            DynamicBuffer.bytes_shifted = rval.consumed;
                            rval.consumed = 0;
                            ecbits = 0;
                        }
                }

            /*
             * Adjust position inside the source buffer.
             */
            if (DynamicBuffer.allocated)
                {
                    DynamicBuffer.offset += rval.consumed;
                    DynamicBuffer.length -= rval.consumed;
                }
            else
                {
                    DynamicBuffer.bytes_shifted += rval.consumed;
                }

            switch (rval.code)
                {
                case RC_OK:
                    if (ecbits)
                        {
                            buffer_shift_left(0, ecbits);
                        }
                    DEBUG("RC_OK, finishing up with %ld+%d",
                        (long)rval.consumed, ecbits);
                    return structure;
                case RC_WMORE:
                    DEBUG(
                        "RC_WMORE, continuing read=%ld, cons=%ld "
                        " with %ld..%ld-%ld..%ld",
                        (long)rd, (long)rval.consumed,
                        (long)DynamicBuffer.offset,
                        (long)DynamicBuffer.length,
                        (long)DynamicBuffer.unbits,
                        (long)DynamicBuffer.allocated);
                    if (!rd)
                        {
                            tolerate_eof--;
                        }
                    continue;
                case RC_FAIL:
                    break;
                }
            break;
        }

    DEBUG("Clean up partially decoded structure");
    ASN_STRUCT_FREE(*pduType, structure);

    new_offset = DynamicBuffer.bytes_shifted + DynamicBuffer.offset;

    /*
     * Print a message and return failure only if not EOF,
     * unless this is our first PDU (empty file).
     */
    if (on_first_pdu || DynamicBuffer.length ||
        new_offset - old_offset > ((iform == INP_XER) ? sizeof("\r\n") - 1 : 0))
        {
#ifdef JUNKTEST
            /*
             * Nothing's wrong with being unable to decode junk.
             * Simulate EOF.
             */
            if (opt_jprob != 0.0)
                {
                    junk_failures++;
                    errno = 0;
                    return 0;
                }
#endif

            DEBUG("ofp %d, no=%ld, oo=%ld, dbl=%ld", on_first_pdu,
                (long)new_offset, (long)old_offset,
                (long)DynamicBuffer.length);
            fprintf(stderr,
                "%s: "
                "Decode failed past byte %ld: %s\n",
                name, (long)new_offset,
                (rval.code == RC_WMORE) ? "Unexpected end of input"
                                        : "Input processing error");
#ifndef ENOMSG
#define ENOMSG EINVAL
#endif
#ifndef EBADMSG
#define EBADMSG EINVAL
#endif
            errno = (rval.code == RC_WMORE) ? ENOMSG : EBADMSG;
        }
    else
        {
            /* Got EOF after a few successful PDUs */
            errno = 0;
        }

    return 0;
}

/* Dump the buffer out to the specified FILE */
static int write_out(const void *buffer, size_t size, void *key)
{
    FILE *fp = (FILE *)key;
    return (fwrite(buffer, 1, size, fp) == size) ? 0 : -1;
}

static int argument_is_stdin(char *av[], int idx)
{
    if (strcmp(av[idx], "-") != 0)
        {
            return 0; /* Certainly not <stdin> */
        }
    else
        {
            /* This might be <stdin>, unless `./program -- -` */
            if (strcmp(av[-1], "--") != 0)
                {
                    return 1;
                }
            else
                {
                    return 0;
                }
        }
}

static FILE *argument_to_file(char *av[], int idx)
{
    return argument_is_stdin(av, idx) ? stdin : fopen(av[idx], "re");
}

static char *argument_to_name(char *av[], int idx)
{
    return argument_is_stdin(av, idx) ? "standard input" : av[idx];
}

#ifdef JUNKTEST
/*
 * Fill bytes with some garbage with specified probability (more or less).
 */
static void junk_bytes_with_probability(uint8_t *buf, size_t size, double prob)
{
    static int junkmode;
    uint8_t *ptr;
    uint8_t *end;
    if (opt_jprob <= 0.0) return;
    for (ptr = buf, end = ptr + size; ptr < end; ptr++)
        {
            int byte = *ptr;
            if (junkmode++ & 1)
                {
                    if ((((double)random() / RAND_MAX) < prob))
                        byte = random() & 0xff;
                }
            else
                {
#define BPROB(b) ((((double)random() / RAND_MAX) < prob) ? b : 0)
                    byte ^= BPROB(0x80);
                    byte ^= BPROB(0x40);
                    byte ^= BPROB(0x20);
                    byte ^= BPROB(0x10);
                    byte ^= BPROB(0x08);
                    byte ^= BPROB(0x04);
                    byte ^= BPROB(0x02);
                    byte ^= BPROB(0x01);
                }
            if (byte != *ptr)
                {
                    DEBUG("Junk buf[%d] %02x -> %02x", ptr - buf, *ptr, byte);
                    *ptr = byte;
                }
        }
}
#endif /* JUNKTEST */
